<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>你好，我叫高压箱</title>
</head>
<center>

<body>

    <div>

        <h1>假装我是一个高压箱</h4>
        <h4>请先选择通信端口</h4>
        <p>刷新页面会自动关闭通信端口</p>
        <button id="com_chooser">选择通信端口</button>
        <input type="text" id="com_product_id" readonly="readonly" placeholder="product id将会显示">
        <input type="text" id="com_vendor_id" readonly="readonly" placeholder="vendor id将会显示">
        <button id="port_closer" disabled="true" onclick="try_to_close_port()">关闭端口</button>
        <br>
    </div>

    <div>
        <span>当前电压：<span id="current_volt"></span><br></span>
        <span>目标电压：<span id="target_volt"></span><br></span>
        <span>变化率：<span id="change_rate"></span><br></span>
        
    </div>
    <div id="debug">
        <button onclick="get_voltage(1);">get_voltage</button>
        <button onclick="get_discharge_num();">get_discharge_num</button>
        <button onclick="set_slew_rate(1,1);">set_slew_rate</button>
        <button onclick="set_target_voltage(1,0);">set_target_voltage</button>
        <br>
        <input type="number" placeholder="你想现在多少伏" id="create_volt">
        <button onclick="generate_voltage_reply();">产生回复电压数据</button>
        <input type="number" placeholder="你想放电几次" class="config_input" id="create_num">
        <button onclick="generate_discharge_num();">产生放电数据</button>
        <!-- <button onclick="create_send_u8_arr(default_send_info);">create_debug_string</button> -->
    </div>

    <script>
        "use strict"
        // 下位机行为假定：
        var change_rate = 0;    // 电压变化速率 500[250, 20000]V
        var current_volt = 0;   // 当前电压值
        var target_volt = 0;    // 目标电压 [0,132000]

        setInterval(() => {
            if(Math.abs(target_volt - current_volt) <= change_rate)
                current_volt = target_volt;
            else if(target_volt > current_volt)
                current_volt += change_rate;
            else
                current_volt -= change_rate;
            document.getElementById("target_volt").innerHTML = target_volt;
            document.getElementById("change_rate").innerHTML = change_rate;
            document.getElementById("current_volt").innerHTML = current_volt;
             
        }, 1000);

        // class HV_comunicator {

        // }
        // 重点实现的功能
        const CMD_SET_HV_OUT_TAR_VAL = 12061;   // send: int hv_ch, float val
        const CMD_SET_HV_OUT_SLEW_RATE = 12063; // send: int hv_ch, float slew_rate
        // const CMD_GET_IP_VAL = 12012;
        const CMS_GET_HV_SAMPLE_VOL_VAL = 12090;    // send: int hv_ch, rec: int hv_ch, float val
        const CMD_GET_DISCHARGE_NUM = 12013;        // send: none, rec: int num

        const ADDRESS_ALL = 0x0041;

        const DEFAULT_DECREASING_RATE = 1000;

        // const 好 = {
        //     降压小黑屋 : 2
        // }

        // console.log(好.降压小黑屋);
        
        // 向高压箱发送的数据
        const default_send_info = {
            Address : ADDRESS_ALL,   // 2 bytes, a number
            Command : CMS_GET_HV_SAMPLE_VOL_VAL,        // 2 bytes, a number
            Pack_id : 100,        // 4 bytes, a number
            Data_field : 0,     // 16 bytes, user created, a uint8 array
            Reserved : 0,       // 4 bytes nothing
            Checksum : 0        // 1 byte, auto created
        }
        // default_send_info.Data_field = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,17]);


        default_send_info.Data_field = concatenate(Uint8Array,new Uint8Array([1,0,0,0]),
                convertTypedArray(new Float32Array([1.33])),new Uint8Array(8));
        // [1,0,0,0,0xf5,0xa8,0xf4,0x42,0,0,0,0,0,0,0,0];

        const rec_info = {
            Address : 0,   // 2 bytes, a number
            Command : 0,        // 2 bytes, a number
            Pack_id : 0,        // 4 bytes, a number
            Data_field : 0,     // 16 bytes, user created, a uint8 array
            ErrorCode : 0,      // 2 bytes, a number
            RespTime : 0,       // 2 bytes, a number
            Checksum : 0        // 1 byte, auto created
        }

        // 改变数组的底层类型，保持字节相同 https://www.coder.work/article/3821011
        // 还是C语言更擅长这些操作
        function convertTypedArray(src, type = Uint8Array) {
            var buffer = new ArrayBuffer(src.byteLength);
            var baseView = new src.constructor(buffer).set(src);
            let arr = new type(buffer);
            return arr;
        }

        // https://www.asciim.cn/hex/float.html

        // 拼接多个数组
        function concatenate(resultConstructor, ...arrays) {
            let totalLength = 0;
            for (let arr of arrays) {
                totalLength += arr.length;
            }
            let result = new resultConstructor(totalLength);
            let offset = 0;
            for (let arr of arrays) {
                result.set(arr, offset);
                offset += arr.length;
            }
            return result;
        }

        // 从待发送的数据对象生成uint8序列
        function create_send_u8_arr(send_info) {
            var result_buf;
            var data_sum;
            if(send_info.Data_field.length != 16) {
                alert("data field length not right!");
                return [0];
            }
            result_buf = concatenate(Uint8Array,
                convertTypedArray(new Uint16Array([send_info.Address])),
                convertTypedArray(new Uint16Array([send_info.Command])),
                convertTypedArray(new Uint32Array([send_info.Pack_id])),
                send_info.Data_field,
                convertTypedArray(new Uint32Array([send_info.Reserved])),
                );
            data_sum = 0;
            for(let i =0;i<28;i++) {
                data_sum += result_buf[i];
            }
            result_buf = concatenate(Uint8Array,result_buf,new Uint8Array([data_sum & 0xFF]));
            
            
            return result_buf;
        }
        
        
        let asd = create_send_u8_arr(default_send_info);
        console.log(asd);

        function analyse_data_receive(arr) {
            var data_sum;
            data_sum = 0;
            for(let i =0;i<28;i++) {
                data_sum += arr[i];
            }
            if(arr[28]!=(data_sum & 0xFF)) {
                console.log("Receive error!");
                return null;
            }
            rec_info.Address = convertTypedArray(arr.subarray(0,2),Uint16Array)[0];
            rec_info.Command = convertTypedArray(arr.subarray(2,4),Uint16Array)[0];
            rec_info.Pack_id = convertTypedArray(arr.subarray(4,8),Uint32Array)[0];
            rec_info.Data_field = arr.subarray(8,24);
            rec_info.ErrorCode = convertTypedArray(arr.subarray(24,26),Uint16Array)[0];
            rec_info.RespTime = convertTypedArray(arr.subarray(26,28),Uint16Array)[0];
            rec_info.Checksum = convertTypedArray(arr.subarray(28,29),Uint8Array)[0];
            return rec_info;
        }
        // console.log(analyse_data_receive(create_send_u8_arr(default_send_info)));

        // 串口
        // 检测浏览器是否支持串口
        if(!("serial" in navigator))
        {
            alert("你的浏览器不支持串口，请用最新版的Chrome或Edge浏览器打开");
        }
        
        // 给端口选择按钮增加点击响应函数
        const com_chooser_button = document.querySelector('#com_chooser');
        var port;   // 端口
        var writer; // 写端口
        var reader;
        let keepReading = true;

        // 把最新的数据放在这里
        var current_voltage = [0,0,0,0];
        var old_discharge_num = 0, new_discharge_num = 0, initialized = 0, sparked_just_now,
            software_discharge_num = 0;


        // 为端口选择按钮增加点击异步响应函数
        com_chooser_button.addEventListener('click', async function() {
            // Prompt user to select any serial port.
            port = await navigator.serial.requestPort();
            if(port != null)
            {
                // Wait for the serial port to open.
                await port.open({ baudRate: 9600 });
                console.log("Open",port,"at baud rate 9600!");
                const {usbProductId, usbVendorId} = port.getInfo();
                console.log("Product id: ",usbProductId,"vendor id: ",usbVendorId);
                document.getElementById("com_product_id").value = usbProductId;
                document.getElementById("com_vendor_id").value = usbVendorId;
                writer = port.writable.getWriter();
                document.getElementById("port_closer").disabled = false;
                // reader = port.readable.getReader();
                // 持续读取


                while(port.readable && keepReading) {
                    reader = port.readable.getReader();
                    try {
                        while(true) {
                            const {value,done} = await reader.read();
                            if(done) {
                                reader.releaseLock();
                                break;
                            }
                            if(value) {
                                console.log("高压箱接收到了数据")
                                console.log(value);
                                var rec_data = analyse_data_receive(value);
                                if(rec_data.Address == ADDRESS_ALL) {
                                    // console.log("here");
                                    switch (rec_data.Command) {
                                        case CMD_SET_HV_OUT_SLEW_RATE:
                                            console.log("高压箱收到了电压上升率");
                                            // console.log("at getting sample voltage");
                                            // channel 取值为1~4，数组序号还要减1
                                            change_rate = convertTypedArray(rec_data.Data_field.subarray(4,8),Float32Array)[0];
                                            break;
                                        case CMD_SET_HV_OUT_TAR_VAL:
                                            console.log("高压箱收到了目标电压");
                                            target_volt = convertTypedArray(rec_data.Data_field.subarray(4,8),Float32Array)[0];
                                            break;
                                        case CMD_GET_DISCHARGE_NUM: // 获取总放电次数
                                            console.log("高压箱收到放电次数请求");
                                            // auto_discharge_num();
                                            break;
                                        case CMS_GET_HV_SAMPLE_VOL_VAL: // 获取当前的电压
                                            console.log("高压箱收到了高压目标请求");
                                            auto_voltage_reply();
                                            break;
                                    
                                        default:
                                            console.log("unexpected command");
                                            break;
                                    }
                                }
                            }
                        }
                    } catch (error) {
                        console.log("An error occurred at reading");
                    } finally {
                        reader.releaseLock();
                    }
                }
            }
        });

        // 端口关闭函数
        async function try_to_close_port(){
            writer.releaseLock();
            reader.releaseLock();
            await port.close();
            document.getElementById("port_closer").disabled = true;
            document.getElementById("com_product_id").value = "";
            document.getElementById("com_vendor_id").value = "";
        }

        async function get_voltage(channel) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMS_GET_HV_SAMPLE_VOL_VAL,        // 2 bytes, a number
                Pack_id : 0,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                new Uint8Array(12)
            );
            await writer.write(create_send_u8_arr(send_info));
            // const {value,done} = await reader.read();
            // if(done) {
            //     reader.releaseLock();
            // }
            // if(value) {
            //     console.log(value);
            //     var rec_data = analyse_data_receive(value);

            // }
        }
        async function get_voltage(channel) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMS_GET_HV_SAMPLE_VOL_VAL,        // 2 bytes, a number
                Pack_id : 0,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                new Uint8Array(12)
            );
            await writer.write(create_send_u8_arr(send_info));
            // const {value,done} = await reader.read();
            // if(done) {
            //     reader.releaseLock();
            // }
            // if(value) {
            //     console.log(value);
            //     var rec_data = analyse_data_receive(value);

            // }
        }
        async function get_discharge_num() {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMD_GET_DISCHARGE_NUM,        // 2 bytes, a number
                Pack_id : 0,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = new Uint8Array(16);
            await writer.write(create_send_u8_arr(send_info));
            // const {value,done} = await reader.read();
            // if(done) {
            //     reader.releaseLock();
            // }
            // if(value) {
            //     console.log(value);
            //     var rec_data = analyse_data_receive(value);

            // }
        }
        async function set_target_voltage(channel,voltage) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMD_SET_HV_OUT_TAR_VAL,        // 2 bytes, a number
                Pack_id : 0,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                convertTypedArray(new Float32Array([voltage])),
                new Uint8Array(8)
            );
            await writer.write(create_send_u8_arr(send_info));
        }

        async function set_slew_rate(channel,slew_rate) {
            var send_info = {
                Address : 0x0041,   // 2 bytes, a number
                Command : CMD_SET_HV_OUT_SLEW_RATE,        // 2 bytes, a number
                Pack_id : 0,        // 4 bytes, a number
                Data_field : 0,     // 16 bytes, user created, a uint8 array
                Reserved : 0,       // 4 bytes nothing
                Checksum : 0        // 1 byte, auto created
            }
            send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([channel])),
                convertTypedArray(new Float32Array([slew_rate])),
                new Uint8Array(8)
            );
            await writer.write(create_send_u8_arr(send_info));
        }


        async function generate_voltage_reply() {
            default_send_info.Command = CMS_GET_HV_SAMPLE_VOL_VAL;
            default_send_info.Data_field = concatenate(Uint8Array,new Uint8Array([1,0,0,0]),
                convertTypedArray(new Float32Array([Number(document.getElementById("create_volt").value)])),new Uint8Array(8));
            var org = create_send_u8_arr(default_send_info);
            await writer.write(org);
            // var str = "";
            // for(let i = 0;i<29;i++) {
            //     str += (org[i].toString(16) + " ");
            // }
            // console.log(str);
        }
        async function generate_discharge_num() {
            default_send_info.Command = CMD_GET_DISCHARGE_NUM;
            default_send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([Number(document.getElementById("create_num").value)])),new Uint8Array(12));
            document.getElementById("create_num").value = Number(document.getElementById("create_num").value) + 1;
            var org = create_send_u8_arr(default_send_info);
            await writer.write(org);
            // var str = "";
            // for(let i = 0;i<29;i++) {
            //     str += (org[i].toString(16) + " ");
            // }
            // console.log(str);
        }

        async function auto_voltage_reply() {
            default_send_info.Command = CMS_GET_HV_SAMPLE_VOL_VAL;
            default_send_info.Data_field = concatenate(Uint8Array,new Uint8Array([4,0,0,0]),
                convertTypedArray(new Float32Array([current_volt])),new Uint8Array(8));
            var org = create_send_u8_arr(default_send_info);
            await writer.write(org);
            // var str = "";
            // for(let i = 0;i<29;i++) {
            //     str += (org[i].toString(16) + " ");
            // }
            // console.log(str);
        }
        async function auto_discharge_num() {
            default_send_info.Command = CMD_GET_DISCHARGE_NUM;
            default_send_info.Data_field = concatenate(Uint8Array,
                convertTypedArray(new Uint32Array([0])),new Uint8Array(12));
            var org = create_send_u8_arr(default_send_info);
            await writer.write(org);
            // var str = "";
            // for(let i = 0;i<29;i++) {
            //     str += (org[i].toString(16) + " ");
            // }
            // console.log(str);
        }
    </script>
</body>
    
</center>
</html>